#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <stdlib.h>
#define CMD_LEN 512
#define MAX_LINE_LEN 256
#define EXIT_ON_INIT_FAIL			\
	perror("Init failed");			\
	exit(EXIT_FAILURE);
#define CAMERA "./camera"

void* hdl = NULL;
FILE* file = NULL;
typedef int (*verify_fn) ();

struct test_case {
	int test_code;
	char* args;
	verify_fn vfy_fn; 
};

test_case tc;

char* get_next_substring(char** str) 
{
	char* head = *str;
	char* next_head = *str;
	while ( *next_head != ';' && *next_head != '\n') ++next_head;
	*next_head = '\0';
	*str = ++next_head;
	return head;
}

void alloc_args(char** args, char* raw_args)
{
	*args = (char*)malloc(strlen(raw_args)+1);
	strcpy(*args, raw_args);
	(*args)[strlen(raw_args)] = '\0';
}

verify_fn get_verify_fn(char* fn_name)
{
	verify_fn fn = (verify_fn)dlsym(hdl, fn_name);
	if (fn) return fn;
	printf("Function %s load failed due to %s.\n", fn_name, dlerror());
	exit(EXIT_FAILURE);
}

int get_next_case(struct test_case* tc)
{
	char buf[MAX_LINE_LEN];
	memset(buf, 0, MAX_LINE_LEN);

	if (tc && tc->args) {free(tc->args); tc->args = NULL;}

	while (fgets(buf, MAX_LINE_LEN, file))
	{
		if (*buf == '#') continue;
		else {
			char* str = (char*)buf;
			tc->test_code = atoi(get_next_substring((char**)&str));
			alloc_args(&tc->args, get_next_substring((char**)&str));
			tc->vfy_fn = get_verify_fn(get_next_substring((char**)&str));
			return 1;
		}
	}
	return 0;
}

int run_case(struct test_case* tc)
{
	char cmd[CMD_LEN];
	memset(cmd, 0, CMD_LEN);
	sprintf(cmd, "%s %d %s > case_output", CAMERA, tc->test_code, tc->args);
	system(cmd);
	int ret = tc->vfy_fn();
	return ret;
}

void init() 
{
	file = fopen("./cases.cfg", "r");
	if (!file) {
		EXIT_ON_INIT_FAIL;
	}

	hdl = dlopen("./test_funcs.so", RTLD_LAZY);
	if (hdl) return;
	fclose(file);
	EXIT_ON_INIT_FAIL;
}

void clean()
{
	if (tc.args) free(tc.args);
	fclose(file);
	dlclose(hdl);
}

int main()
{
	int seq = 0;
	//test_case tc;
	memset(&tc, 0, sizeof(tc));
	init();

	printf("Start to test camera.\n");
	while (get_next_case(&tc)) {
		if (run_case(&tc)) {
			printf("Case %d passed test.\n", ++seq);
		} else {
			printf("Case %d failed\n", ++seq);
			exit(EXIT_FAILURE);
		}
	}
	printf("End of testing camera.\n");
	clean();
	return 0;
}
